package org.koin.core.registry;

import org.koin.core.Koin;
import org.koin.core.annotation.KoinInternalApi;
import org.koin.core.definition.BeanDefinition;
import org.koin.core.error.DefinitionOverrideException;
import org.koin.core.error.NoScopeDefFoundException;
import org.koin.core.error.ScopeAlreadyCreatedException;
import org.koin.core.module.Module;
import org.koin.core.qualifier.Qualifier;
import org.koin.core.scope.Scope;
import org.koin.core.scope.ScopeDefinition;
import java.util.*;
import java.util.function.Consumer;

@KoinInternalApi
public class ScopeRegistry {

    private Koin _koin;
    private Map<String, ScopeDefinition> _scopeDefinitions = new HashMap<>();
    private HashMap<String, Scope> _scopes = new HashMap<>();
    private ScopeDefinition _rootScopeDefinition = null;
    private Scope _rootScope = null;

    public ScopeRegistry(Koin _koin) {
        this._koin = _koin;
    }

    public Map<String, ScopeDefinition> getScopeDefinitions() {
        return _scopeDefinitions;
    }

    public Scope getRootScope() {
        if (_rootScope == null) {
            throw new IllegalStateException("No root scope");
        }
        return _rootScope;
    }

    public int size() {
        int size = 0;
        for (ScopeDefinition scopeDefinition : _scopeDefinitions.values()) {
            size += scopeDefinition.size();
        }
        return size;
    }

    public void loadModules(Iterable<Module> modules) throws DefinitionOverrideException {
        for (Module module : modules) {
            if (!module.isLoaded()) {
                loadModule(module);
            } else {
                _koin.getLogger().error("module '" + module + "' already loaded!");
            }
        }
    }

    private void loadModule(Module module) throws DefinitionOverrideException {
        declareScopeDefinitions(module.getScopes());
        declareBeanDefinitions(module.getDefinitions());
        module.setLoaded(true);
    }

    public void declareScopeDefinitions(List<Qualifier> scopes) {

        for (Qualifier qualifier : scopes) {
            createScopeDefinition(qualifier);
        }
    }

    private void declareBeanDefinitions(HashSet<BeanDefinition> definitions)
            throws DefinitionOverrideException {
        for (BeanDefinition bean : definitions) {
            declareDefinition(bean);
        }
    }

    public void declareDefinition(BeanDefinition bean) throws DefinitionOverrideException {
        ScopeDefinition scopeDef = _scopeDefinitions.get(bean.getScopeQualifier().value);
        if (scopeDef == null) {
            throw new IllegalStateException("Undeclared scope definition for definition: " + bean);
        }
        scopeDef.save(bean);
        _scopes.values().forEach(new Consumer<Scope>() {
            @Override
            public void accept(Scope scope) {
                if (scope.getScopeDefinition().equals(scopeDef)) {
                    scope.loadDefinition(bean);
                }
            }
        });
    }

    private void createScopeDefinition(Qualifier qualifier) {
        ScopeDefinition def = new ScopeDefinition(qualifier);
        if (_scopeDefinitions.get(qualifier.value) == null) {
            _scopeDefinitions.put(qualifier.value, def);
        }
    }

    public void createRootScopeDefinition() {
        if (_rootScopeDefinition == null) {
            ScopeDefinition scopeDefinition = ScopeDefinition.rootDefinition();
            _scopeDefinitions.put(ScopeDefinition.ROOT_SCOPE_QUALIFIER.value, scopeDefinition);
            _rootScopeDefinition = scopeDefinition;
        } else {
            throw new IllegalStateException("Try to recreate Root scope definition");
        }
    }

    public void createRootScope() throws ScopeAlreadyCreatedException, NoScopeDefFoundException {
        if (_rootScope == null) {
            _rootScope =
                    createScope(ScopeDefinition.ROOT_SCOPE_ID,
                            ScopeDefinition.ROOT_SCOPE_QUALIFIER, null);
        } else {
            throw new IllegalStateException("Try to recreate Root scope");
        }
    }

    public Scope getScopeOrNull(String scopeId) {
        return _scopes.get(scopeId);
    }

    public Scope createScope(String scopeId, Qualifier qualifier, Object source)
            throws ScopeAlreadyCreatedException, NoScopeDefFoundException {
        if (_scopes.containsKey(scopeId)) {
            throw new ScopeAlreadyCreatedException("Scope with id '" + scopeId +
                    "' is already created");
        }

        ScopeDefinition scopeDefinition = _scopeDefinitions.get(qualifier.value);
        if (scopeDefinition != null) {
            Scope createdScope = createScope(scopeId, scopeDefinition, source);
            _scopes.put(scopeId, createdScope);
            return createdScope;
        } else {
            throw new NoScopeDefFoundException("No Scope Definition found for qualifer '"
                    + qualifier.value + "'");
        }
    }

    private Scope createScope(String scopeID, ScopeDefinition scopeDefinition, Object source) {
        Scope scope = new Scope(scopeID, scopeDefinition, _koin);
        scope.setSource(source);
        ArrayList<Scope> links = new ArrayList<>();
        if (_rootScope != null) {
            links.add(_rootScope);
        }
        scope.create(links);
        return scope;
    }

    public void deleteScope(String scopeId) {
        _scopes.remove(scopeId);
    }

    public void deleteScope(Scope scope) {
        scope.getScopeDefinition().removeExtras();
        _scopes.remove(scope.getId());
    }

    public void close() {
        clearScopes();
        _scopes.clear();
        _scopeDefinitions.clear();
        _rootScopeDefinition = null;
        _rootScope = null;
    }

    private void clearScopes() {
        _scopes.values().forEach(new Consumer<Scope>() {
            @Override
            public void accept(Scope scope) {
                scope.clear();
            }
        });
    }

    public void unloadModules(Iterable<Module> modules) {
        for (Module module : modules) {
            unloadModules(module);
        }
    }

    public void unloadModules(Module module) {
        for (BeanDefinition bean : module.getDefinitions()) {
            ScopeDefinition scopeDefinition = _scopeDefinitions.get(bean.getScopeQualifier().value);
            if (scopeDefinition == null) {
                throw new IllegalStateException("Can't find scope for definition " + bean);
            }
            scopeDefinition.unloadDefinition(bean);
            for (Scope scope : _scopes.values()) {
                if(scope.getScopeDefinition().getQualifier().equals(scopeDefinition
                        .getQualifier())){
                    scope.dropInstance(bean);
                }
            }
        }
        module.setLoaded(false);
    }
}
